package com.ssl.basics.readwrite;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 描述：读写锁
 *
 * @author ssl
 * @create 2021/7/13 0013.
 */
@Slf4j(topic = "com.ssl.thread")
public class ReadWriteLockDemo {


    private static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    static Lock readLock = readWriteLock.readLock();
    static Lock writeLock = readWriteLock.writeLock();


    public static void main(String[] args) throws InterruptedException {
        //  testReadLock();
        // testWriteLock();
        testWriteDemotionLock();
    }

    public static void testWriteDemotionLock() throws InterruptedException {
        // *** 首先明确一点，同一个线程中，读锁+读锁不互斥，写锁+写锁不互斥，读锁+写锁成为锁降级，写锁+读锁会阻塞 ***
        // 1、先获取读锁，再获取写锁，会阻塞，因为读锁不能升级为写锁
/*
        readLock.lock();
        log.info("{} 获取读锁成功···", Thread.currentThread().getName());
        //readLock.unlock();
        writeLock.lock();
        log.info("{} 获取写锁成功··", Thread.currentThread().getName());
*/

        // 2、先获取写锁，再获取读锁，会成功，因为写锁可以降级为读锁
        writeLock.lock();
        log.info("{} 获取写锁成功···", Thread.currentThread().getName());
        readLock.lock();
        log.info("{} 获取读锁成功（因为写锁能降级为读锁）···", Thread.currentThread().getName());

    }

    /**
     * 写锁测试：测试结果表明，写锁是独占锁，当有一个线程获取到写锁没有释放时，其他线程都会阻塞，即使是读锁，也会被阻塞
     */
    public static void testWriteLock() {
        log.info("{} 开始获取写锁···", Thread.currentThread().getName());
        writeLock.lock();
        log.info("{} 获取写锁成功···", Thread.currentThread().getName());

        writeLock.lock();
        log.info("{} 再次获取写锁成功（因为读写锁都是可重入的）···", Thread.currentThread().getName());

        // 写锁未释放，其他线程获取写锁会被阻塞
        new Thread(() -> {
            log.info("{} 尝试获取写锁···", Thread.currentThread().getName());
            writeLock.lock();
            log.info("{} 获取写锁成功···", Thread.currentThread().getName());
        }, "tt1").start();

        // 写锁未释放，其他线程读锁获取不到：如果想要可以获取到读锁，建议用 StampedLock
        new Thread(() -> {
            log.info("{} 尝试获取读锁···", Thread.currentThread().getName());
            readLock.lock();
            log.info("{} 获取写读成功···", Thread.currentThread().getName());
        }, "tt2").start();
    }


    /**
     * 读锁测试:测试结果表明，读锁是共享锁，当有一个线程获取到读锁没有释放时，其他线程可以继续获取到读锁,但是如果其他线程继续获取写锁时，也一样会被阻塞
     */
    public static void testReadLock() {
        log.info("{} 开始获取读锁···", Thread.currentThread().getName());
        readLock.lock();
        log.info("{} 获取读锁成功···", Thread.currentThread().getName());
        //readLock.unlock(); 这里即使没有释放读锁，其他线程也还是可以读的
        new Thread(() -> {
            log.info("{} 尝试获取读锁···", Thread.currentThread().getName());
            readLock.lock();
            log.info("{} 获取读锁成功···", Thread.currentThread().getName());
        }, "t1").start();

        // 因为没有释放读锁，获取写锁会被阻塞
        new Thread(() -> {
            log.info("{} 尝试获取写锁···", Thread.currentThread().getName());
            writeLock.lock();
            log.info("{} 获取写锁成功···", Thread.currentThread().getName());
        }, "t2").start();
    }
}
